Interactive Mandelbrot Set¶
Mandelbrot Set is a fractal, which is self-similar. If you zoom in on a fractal object it will look similar, (possibly rotated), or exactly like the original shape.
There are many other known fractals.
This notebook uses Matplotlib's interactive widget backend. If it is not available, open a terminal from Anaconda Navigator and install it with:
conda install -c conda-forge ipympl
After installation, restart Jupyter. Use the toolbar below the plot to pan, select a rectangular zoom region, return to the home view, or save the figure. When a zoom or pan is completed, the selected region is recomputed at the full image resolution rather than merely stretching the existing pixels.
In [1]:
import numpy as np
from numba import njit # This is the new line with numba
from numba import prange
@njit # this is an alias for @jit(nopython=True)
def Mand(z0, max_steps):
z = 0j # no need to specify type.
# To initialize to complex number, just assign 0j==i*0
for itr in range(max_steps):
if abs(z)>2:
return itr
z = z*z + z0
return max_steps
@njit(parallel=True)
def Mandelbrot3(data, ext, max_steps):
"""
ext[4] -- array of 4 values [min_x,max_x,min_y,max_y]
Nxy -- int number of points in x and y direction
max_steps -- how many steps we will try at most before we conclude the point is in the set
"""
Nx,Ny = data.shape # the 2D array is already allocated; obtain its dimensions
for i in prange(Nx): # distribute independent rows among threads
for j in range(Ny): # serial loop within each worker
x = ext[0] + (ext[1]-ext[0])*i/(Nx-1.)
y = ext[2] + (ext[3]-ext[2])*j/(Ny-1.)
# creating complex number of the fly
data[i,j] = Mand(x + y*1j, max_steps)
# data now contains integers.
# MandelbrotSet has value 1000, and points not in the set have value <1000.
In [15]:
%matplotlib widget
# special line for inline plotting
import matplotlib.pyplot as plt
Nxy = 1000
max_steps = 1000
initial_ext = np.array([-2.0, 1.0, -1.0, 1.0])
# Compile the Numba functions with a small calculation before computing the full image.
Mandelbrot3(np.zeros((10,10)), initial_ext, 10)
data = np.zeros((Nxy,Nxy))
Mandelbrot3(data, initial_ext, max_steps)
fig, ax = plt.subplots(figsize=(10,7)) # 10inch x 7 inch size
fig.canvas.layout.width = '100%' # size inside this browser
fig.canvas.layout.height = '700px'
# first plotting as usual
im = ax.imshow(-np.log(data.T), extent=initial_ext, aspect='equal', origin='lower', cmap=plt.cm.coolwarm)
ax.set_xlabel(r'$\mathrm{Re}(z_0)$')
ax.set_ylabel(r'$\mathrm{Im}(z_0)$')
ax.set_title('Use the toolbar to pan or select a zoom region')
ax.set_autoscale_on(False) # crucial to be able to resize
# Store the limits of the most recently computed image. This prevents an
# ordinary mouse click, which does not change the view, from recomputing it.
last_ext = initial_ext.copy()
def update_after_navigation(event):
if event.inaxes is not ax: # the event occurred somewhere other than this particular plot?
# event.inaxes can be - ax if the pointer is inside this plot
# - another Axes object if the figure contains several plots
# - None if the pointer is outside every plotting area, such as over the toolbar or margins
return
xmin, xmax = ax.get_xlim() # current limits in this axis
ymin, ymax = ax.get_ylim()
new_ext = np.array([xmin, xmax, ymin, ymax]) # new size
if np.allclose(new_ext, last_ext): # if size is accidentaly the same, we do nothing
return
ax.set_title('Computing the selected region...')
fig.canvas.draw_idle() # tells the interactive backend that the displayed canvas needs to be refreshed. It actually only refreshes the title
# magnification = abs((initial_ext[1]-initial_ext[0])/(new_ext[1]-new_ext[0]))
Mandelbrot3(data, new_ext, max_steps)
im.set_data(-np.log(data.T))
im.set_extent(new_ext)
last_ext[:] = new_ext # remember this ext now
ax.set_title('Use the toolbar to select another zoom region max_steps='+str(max_steps))
fig.canvas.draw_idle() # finally replot with the new data.
# Recompute once after the user finishes a pan or rectangular zoom. Connecting the event with our own function
navigation_connection = fig.canvas.mpl_connect('button_release_event', update_after_navigation)
plt.show()
In [ ]: